#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

using namespace std;

// init
//
// SDA 44
// SCL 45
// echo SDA > /sys/class/gpio/export
// echo in > /sys/class/gpio/gpio$SDA/direction
// echo SCL > /sys/class/gpio/export
// echo in > /sys/class/gpio/gpio$SCL/direction

// if default is output and val=1, echo out > direction make val=0

#define SDA_VALUE "/sys/class/gpio/gpio17/value"
#define SDA_DIRECT "/sys/class/gpio/gpio17/direction"
#define SCL_VALUE "/sys/class/gpio/gpio20/value" // hardware problem
#define SCL_DIRECT "/sys/class/gpio/gpio20/direction"

static int debug;

static int i2c_get_sda(void)
{
    int val;
    ifstream infile(SDA_VALUE);
    infile >> val;
    return val;
}

static void i2c_set_sda(int val)
{
    ofstream outfile(SDA_DIRECT);
    if (val == 1)
        outfile << "in";
    else
        outfile << "out";   // default val=0
}

static void i2c_set_scl(int val)
{
    ofstream outfile(SCL_VALUE);
    if (val == 1)
        outfile << "1";
    else
        outfile << "0";
}

static void i2c_delay(long msec)
{
    struct timespec tim;
    tim.tv_sec  = 0;
    tim.tv_nsec = msec * 1000000L;
    nanosleep(&tim, NULL);
}

//    ___
//sda:   |_______
//    _______
//scl:       |___
//
static void i2c_start(void)
{
    i2c_set_scl(1);
    i2c_set_sda(1);
    i2c_delay(2);
    i2c_set_sda(0);
    i2c_delay(2);
    i2c_set_scl(0);
    i2c_delay(2);
}

//        ___
//sda:___|
//    _______
//scl:
//
static void i2c_stop(void)
{
    i2c_delay(1);
    i2c_set_scl(1);
    i2c_set_sda(0);
    i2c_delay(2);
    i2c_set_sda(1);
    i2c_delay(2);
}

//
//sda:|--------------
//         ______
//scl:____|      |___
//
static int i2c_rw(int is_read, unsigned char *val)
{
    int ack = 0;

    if (is_read)
        i2c_set_sda(1);

    for (int i = 0; i < 8; ++i) {
        if (!is_read) i2c_set_sda((*val >> (7 - i)) & 0x01);
        i2c_delay(1);
        i2c_set_scl(1);
        i2c_delay(2);
        if (is_read) *val = (*val << 1) | i2c_get_sda();
        i2c_set_scl(0);
        i2c_delay(1);
    }
    i2c_set_sda(!is_read); // send ack if is_read = 1
    i2c_delay(1);
    i2c_set_scl(1);
    i2c_delay(2);
    if (!is_read) ack = i2c_get_sda();
    i2c_set_scl(0);

    i2c_delay(1);
    i2c_set_sda(1); // clean ack or nothing

    return ack;
}

static unsigned char mcu_reg_read(unsigned char addr)
{
    unsigned char val = 0;

    addr = (addr << 1) + 1; // lsb = 1 mean read
    while (true) {
        i2c_start();
        if (i2c_rw(0, &addr) != 0) {
            if (debug)
                cerr << "read: phase addr no ack\n";
            continue;
        }
        i2c_rw(1, &val); // send ack at this phase
        i2c_stop();
        return val;
    }
}

static void mcu_reg_write(unsigned char addr, unsigned char val)
{
    addr = addr << 1; // lsb = 0 mean write
    while (true) {
        i2c_start();
        if (i2c_rw(0, &addr) != 0) {
            if (debug)
                cerr << "write: phase addr no ack\n";
            continue;
        }
        if (i2c_rw(0, &val) != 0) {
            if (debug)
                cerr << "write: phase data no ack\n";
            continue;
        }
        i2c_stop();
        return;
    }
}

typedef union {
    short si;
    unsigned char uc[2];
} val_t;

// hc-mcu read goal
// hc-mcu read current
// hc-mcu read humidity
// hc-mcu write 3200 [-d]

int main(int argc, char *argv[]) {
    val_t val_tmp;
    string arg_tmp;
    float f_tmp;

    if (argc == 4) debug = 1;
    else debug = 0;
    if (argc < 3)
        goto usage;

    arg_tmp = argv[1];
    if (arg_tmp.find("read") == 0) {
        int reg;
        arg_tmp = argv[2];
        if (arg_tmp.find("goal") == 0) reg = 0;
        else if (arg_tmp.find("current") == 0) reg = 2;
        else if (arg_tmp.find("humidity") == 0) reg = 4;
        else goto usage;
        val_tmp.uc[0] = mcu_reg_read(reg);
        val_tmp.uc[1] = mcu_reg_read(reg + 1);
        f_tmp = val_tmp.si / 100.0;
        cout << f_tmp << endl;
    } else if (arg_tmp.find("write") == 0) {
        stringstream ss(argv[2]);
        ss >> f_tmp;
        val_tmp.si = f_tmp * 100;
        mcu_reg_write(0, val_tmp.uc[0]);
        mcu_reg_write(1, val_tmp.uc[1]);
    } else
        goto usage;

    return 0;

 usage:
    cout << "Usage:\n\thc-mcu read goal\n"
        "\thc-mcu read current\n"
        "\thc-mcu read humidity\n"
        "\thc-mcu write 26.00 [-d]\n";
    return -1;
}
